#include "deployment.h"
#include <algorithm>
#include "ship_template.h"
#include "slot_template.h"
#include "fleet.h"
#include "ship.h"
#include "json/value.h"

using namespace std;

Deployment::Deployment( string _name ) : CostKeeper(0), name(_name) {
	// Create Unlisted Fleet
	Fleet * unlisted = new Fleet(string("Unlisted"));
	fleets.push_back(unlisted);
	unlisted->registerObserver(this);

	ownTemplateSet = 0;
}

Deployment::Deployment( const Json::Value & _dep, string _name ) : CostKeeper(0) {
	name = _name;
	unsigned fleetOffset = 0;

	// Force existence of Unlisted
	if ( _dep["Fleets"].size() == 0 || _dep["Fleets"][0u]["Name"].asString() != "Unlisted" ) {
		Fleet * unlisted = new Fleet(string("Unlisted"));
		fleets.push_back(unlisted);
		unlisted->registerObserver(this);
		fleetOffset = 1;
	}
		
	// Load all other Fleets
	for ( unsigned i = 0; i < _dep["Fleets"].size(); i++ ) {
		Fleet * fleet = new Fleet(_dep["Fleets"][i]);
		fleets.push_back(fleet);
		fleet->registerObserver(this);
	}

	// Load all Templates
	for ( unsigned int i = 0; i < _dep["Templates"].size(); i++ ) {
		ShipTemplate * templ = new ShipTemplate(_dep["Templates"][i]);	
		templates.push_back(make_tuple(templ, initializer_list<Ship*>{}));
	}

	// Load all Ships
	for ( unsigned int i = 0; i < _dep["Ships"].size(); i++ ) {
		// Get required template pointer
		ShipTemplate * templ = get<0>(templates.at(_dep["Ships"][i]["Template"].asInt()));
		// Instantiate Ship
		Ship * ship = new Ship(_dep["Ships"][i]["Ship"], *templ);
		// Add ship to Template list
		get<1>(templates.at(_dep["Ships"][i]["Template"].asInt())).push_back(ship);
		// Get required Fleet	
		Fleet * fleet = fleets.at(_dep["Ships"][i]["Fleet"].asInt() + fleetOffset);
		// Add ship to fleet
		fleet->addShip(ship);
		// Add ship to ship list
		ships.push_back(make_tuple(ship,fleet));	
	}

	// Remove all empty Templates
	remove_if(begin(templates),end(templates),
			[](decltype(*begin(templates)) x){return !get<1>(x).size();});

	if ( _name == "" ) name = _dep["Name"].asString();

	ownTemplateSet = 0;
}

Deployment::~Deployment() {
	for ( auto & it : fleets )  	delete it;
	for ( auto & it : ships )		delete get<0>(it);
	for ( auto & it : templates )	delete get<0>(it);
}

Json::Value Deployment::convertToJson() const {
	Json::Value deploy;
	unsigned int i;
	
	deploy["Name"] = name;
	deploy["Type"] = "Deployment";
	deploy["Cost"] = getCost();

	// Save all Fleets
	i = 0;
	for ( auto & it : fleets ) {
		deploy["Fleets"][i] = it->convertToJson(false);
		i++;
	}
	// Save all Templates
	i = 0;
	for ( auto & it : templates ) {
		deploy["Templates"][i] = get<0>(it)->convertToJson();
		i++;
	}
	// Save all Ships and connect them to already saved Fleets & Templates
	i = 0;
	Ship * ship;
	for ( auto & it : ships ) {
		ship = get<0>(it);
		// We don't need to save its template
		deploy["Ships"][i]["Ship"] = ship->convertToJson(false);
		// Find the Ship's Template offset
		auto templateIt = find_if(begin(templates), end(templates),
			[ship](decltype(*begin(templates)) x){return get<0>(x) == ship->getShipTemplate();});
		if ( templateIt == templates.end() ) throw runtime_error("Deployment::convertToJson()");

		int templateOffset = templateIt - templates.begin();
		// Save the offset
		deploy["Ships"][i]["Template"] = templateOffset;
		// Find the Ship's Fleet offset
		auto fleetIt = find(begin(fleets), end(fleets), get<1>(it) );
		if ( fleetIt == fleets.end() ) throw runtime_error("Deployment::convertToJson()");

		int fleetOffset = fleetIt - fleets.begin();
		// Save the offset
		deploy["Ships"][i]["Fleet"] = fleetOffset;
		i++;
	}

	return deploy;
}

// TEMPLATE FUNCTIONS

void Deployment::addShipTemplate( ShipTemplate * _template, Ship * _ship ) {
	if ( _template == 0 || _ship == 0 ) throw invalid_argument("Deployment::addShipTemplate(ShipTemplate*,Ship*)");

	templates.push_back(make_tuple(_template, initializer_list<Ship*>{_ship}));
}

// FLEET FUNCTIONS

void Deployment::addFleet( string _fleetName ) {
	Fleet * fleet = new Fleet(_fleetName);
	fleets.push_back(fleet);
	fleet->registerObserver(this);
}

void Deployment::rmFleet( int _fleet, bool _delete ) {
	if ( _fleet == 0 ) throw invalid_argument("Deployment::rmFleet(int,int)");

	// Move all ships of this fleet into Unlisted
	// or delete them
	while ( fleets.at(_fleet)->getShipsNumber() > 0 ) {
		if ( _delete )
			rmShip( 0, _fleet );
		else
			mvShip( 0, _fleet, 0 ); // Move to unlisted
	}

	fleets.at(_fleet)->unregisterObserver(this);

	delete fleets.at(_fleet);

	// Rm fleet	
	fleets.erase(fleets.begin() + _fleet);
}

void Deployment::renameFleet( int _fleet, string _name ) {
	fleets.at(_fleet)->name = _name;
}

// SHIP FUNCTIONS
int Deployment::shipFleetIndex( int _ship, int _fleet ) {
	// Get ship pointer
	const Ship * ship = fleets.at(_fleet)->getShip(_ship);

	// Find ship pointer in ship list
	auto it = find_if(begin(ships), end(ships),
				[ship](decltype(*begin(ships)) x){return get<0>(x) == ship;});

	if ( it == ships.end() ) throw runtime_error("Deployment::rmShip(Ship*)");

	// Call rmShip with an integer argument
	return ( it - begin(ships) );
}

void Deployment::addShip( string _name, int _thrust, const ShipTemplate & _template, int _fleet ) {
	ShipTemplate * t = 0;

	// Check if we have an equivalent template already
	auto it = find_if(begin(templates), end(templates),
				[_template](decltype(*begin(templates)) x){return (*get<0>(x)) == _template;});

	bool isNewTemplate;

	// If we don't have this template, create it
	if ( ( isNewTemplate = (it == templates.end()) ) )
		t = new ShipTemplate(_template);
	else	
		t = get<0>(*it);

	// Build new Ship
	Ship * ship = new Ship(_name, _thrust, *t);

	// If template was new now we can insert it
	if ( isNewTemplate )
		addShipTemplate( t, ship );
	// Otherwise we add the ship to its list
	else
		get<1>(*it).push_back(ship);

	// Get Fleet
	Fleet * fleet = fleets.at(_fleet);

	// Add Ship into Fleet
	fleet->addShip(ship);
	
	// Insert Ship + Fleet
	ships.push_back(make_tuple(ship,fleet));	
}

void Deployment::rmShip( int _ship ) {
	Ship * toRm;
	Fleet * fleet;
	
	tie( toRm, fleet ) = ships.at(_ship);	

	// Remove Ship from ship's list
	ships.erase(ships.begin() + _ship);

	// Remove Ship from its Fleet
	fleet->rmShip(toRm);

	// Get Ship's Template
	const ShipTemplate * shipT = toRm->getShipTemplate();

	// Get iterator to tuple with the right template
	// The lambda is because we have to search in tuples
	auto it = find_if(begin(templates), end(templates),
				[shipT](decltype(*begin(templates)) x){return get<0>(x) == shipT;});

	if ( it == templates.end() ) throw runtime_error("Deployment::rmShip(int)");

	// Remove the ship pointer from its template list
	remove_if(begin(get<1>(*it)),end(get<1>(*it)),
				[toRm](Ship * x){return x == toRm;});

	// If the template has no ships anymore, delete it
	if ( ! get<1>(*it).size() ) {
		delete get<0>(*it);
		templates.erase(it);
	}

	// Delete Ship
	delete toRm;
}

void Deployment::rmShip( int _ship, int _fleet) {
	// Get ship pointer
	const Ship * ship = fleets.at(_fleet)->getShip(_ship);

	// Find ship pointer in ship list
	auto it = find_if(begin(ships), end(ships),
				[ship](decltype(*begin(ships)) x){return get<0>(x) == ship;});

	if ( it == ships.end() ) throw runtime_error("Deployment::rmShip(Ship*)");

	// Call rmShip with an integer argument
	rmShip( it - begin(ships) );
}

void Deployment::mvShip( int _ship, int _toFleet ) {
	Fleet * f = get<1>(ships.at(_ship));

	auto it = find(begin(fleets), end(fleets), f);
	if ( it == fleets.end() ) throw runtime_error("Deployment::mvShip(int,int)");

	mvShip(_ship, it - begin(fleets), _toFleet);
}

void Deployment::mvShip( int _ship, int _fromFleet, int _toFleet ) {
	Fleet * toFleet = fleets.at(_toFleet);
	Ship * ship = get<0>(ships.at(_ship));

	fleets.at(_fromFleet)->rmShip(ship);
	toFleet->addShip(ship);

	get<1>(ships.at(_ship)) = toFleet;
}

void Deployment::renameShip( int _ship, string _name ) {
	get<0>(ships.at(_ship))->name = _name;
}

void Deployment::renameShip( int _ship, int _fleet, string _name ) {
	const Ship * cShip = fleets.at(_fleet)->getShip(_ship);	
	auto ship = find_if(begin(ships),end(ships),
			[cShip](decltype(*begin(ships)) x){return get<0>(x) == cShip;});

	if ( ship == ships.end() ) throw runtime_error("Deployment::renameShip(int,int,string)");

	get<0>(*ship)->name = _name;
}

// SHIP LOADOUT

void Deployment::editShipLoadout( int _ship, int _side, int _slot, int _loadout ) {
	get<0>(ships.at(_ship))->setSlotLoadout(_side,_slot,_loadout);
}

void Deployment::editShipLoadout( int _ship, int _fleet, int _side, int _slot, int _loadout ) {
	int index = shipFleetIndex(_ship,_fleet);
	editShipLoadout(index,_side,_slot,_loadout);
}

void Deployment::editShipTower( int _ship, int _tower, int _loadout ) {
	get<0>(ships.at(_ship))->setControlTowerLoadout(_tower,_loadout);
}
void Deployment::editShipTower( int _ship, int _fleet, int _tower, int _loadout ) {
	int index = shipFleetIndex(_ship,_fleet);
	editShipTower(index,_tower,_loadout);
}

// SUMMARIES

vector<array<string,4>> Deployment::getShipSummaries() const {
	vector<array<string,4>> summaries;

	array<string,4> temp;
	for ( auto & it : ships ) {
 		swap_ranges( temp.begin(), temp.begin()+3, get<0>(it)->getSummary().begin() );
		temp[3] = temp[2];
		temp[2] = get<1>(it)->name;
		summaries.push_back( temp );
	}

	return summaries;
}

vector<array<string,4>> Deployment::getFleetSummaries() const {
	vector<array<string,4>> summaries;

	for ( auto & it : fleets )
		summaries.push_back( it->getSummary() );

	return summaries;
}

vector<array<string,3>> Deployment::getShipTemplateSummaries() const {
	vector<array<string,3>> summaries;

	for ( auto & it : templates )
		summaries.push_back( get<0>(it)->getSummary() );

	return summaries;
}

// GETTERS

const Ship * Deployment::getShip( int _ship ) const {
	const Ship * t = get<0>(ships.at(_ship));
	return t;
}

const Ship * Deployment::getShip( int _ship, int _fleet ) {
	return getFleet(_fleet)->getShip(_ship);
}

const ShipTemplate * Deployment::getShipTemplate( int _template ) const {
	const ShipTemplate * t = get<0>(templates.at(_template));
	return t;
}

const Fleet * Deployment::getFleet(int _fleet) const {
	const Fleet * t = fleets.at(_fleet);
	return t;
}

int Deployment::getShipsNumber() const {
	return ships.size();
}

int Deployment::getFleetsNumber() const {
	return fleets.size();
}

int Deployment::getRealFleetsNumber() const {
	// Unlisted Ships do not count as a Fleet
	return fleets.size() - 1;
}

vector<string> Deployment::getShipNames() const {
	vector<string> names;

	for ( auto &it : ships )
		names.push_back( get<0>(it)->name );

	return names;
}

vector<string> Deployment::getFleetNames() const {
	vector<string> names;

	for ( auto &it : fleets )
		names.push_back( it->name );

	return names;
}

vector<string> Deployment::getRealFleetNames() const {
	vector<string> names = getFleetNames();
	names.erase(names.begin());
	return names;
}

vector<string> Deployment::getShipTemplateNames() const {
	vector<string> names;

	for ( auto &it : templates )
		names.push_back( get<0>(it)->name );
	
	return names;
}
